using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections;
using System.Data.Entity.Core.Objects.DataClasses;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using System.Xml.Serialization;
using System.Web.Script.Serialization;

namespace System.Web.Mvc
{
	public class PropertyCache : IEnumerable<PropertyDescriptor>
	{

		private List<PropertyDescriptor> Properties { get; set; }

		private List<PropertyDescriptor> HiddenProperties { get; set; }

		public void Show(params string[] properties)
		{
			Swap(HiddenProperties, Properties, properties);
		}

		public void Hide(params string[] properties)
		{
			Swap(Properties, HiddenProperties, properties);
		}

		#region private void Swap(List<PropertyDescriptor> src,List<PropertyDescriptor> dest,string property)
		private void Swap(List<PropertyDescriptor> src, List<PropertyDescriptor> dest, params string[] properties)
		{
			foreach (var property in properties)
			{
				if (dest.Any(x => x.Name == property))
					continue;
				var p = src.FirstOrDefault(x => x.Name == property);
				dest.Add(p);
				src.Remove(p);
			}
		}
		#endregion

		private static Dictionary<Type, PropertyCache> Defaults = new Dictionary<Type, PropertyCache>(); 

		private PropertyCache(Type type){
			ResolveProperties(type);
		}

		private PropertyCache(PropertyCache cache){
			Properties = new List<PropertyDescriptor>(cache.Properties);
			HiddenProperties = new List<PropertyDescriptor>(cache.HiddenProperties);
		}

		public static PropertyCache Create(Type type)
		{
			PropertyCache pc = null;
			lock(Defaults){
				if(Defaults.TryGetValue(type,out pc)){
					
					return new PropertyCache(pc);
				}

				pc = new PropertyCache(type);
				Defaults[type] = pc;
				return new PropertyCache(pc);
			}
		}


	

		private void ResolveProperties(Type type)
		{
			Properties = new List<PropertyDescriptor>();
			HiddenProperties = new List<PropertyDescriptor>();

			Type et = typeof(EntityObject);
			foreach (PropertyDescriptor pd in TypeDescriptor.GetProperties(type))
			{
				Type propertyType = pd.PropertyType;
				if (propertyType.IsEnum)
					continue;
				if (propertyType.Name.StartsWith("EntityReference"))
					continue;
				if (propertyType.Assembly == et.Assembly)
					continue;
				if (pd.Attributes.OfType<ScriptIgnoreAttribute>().Any())
					continue;
				if (pd.Attributes.OfType<XmlIgnoreAttribute>().Any())
					continue;


				Type t = type;
				PropertyInfo pinfo = t.GetProperty(pd.Name);
				if (pinfo != null && pinfo.DeclaringType == et)
					continue;

				if (typeof(EntityObject).IsAssignableFrom(propertyType))
				{
					if (t.GetProperty(pd.Name + "Reference") != null)
					{
						HiddenProperties.Add(pd);
						continue;
					}
				}

				Properties.Add(pd);
			}
		}	


		#region public IEnumerator<PropertyDescriptor>  GetEnumerator()
		public IEnumerator<PropertyDescriptor> GetEnumerator()
		{
			return Properties.GetEnumerator();
		}
		#endregion


		#region IEnumerator  IEnumerable.GetEnumerator()
		IEnumerator IEnumerable.GetEnumerator()
		{
			return Properties.GetEnumerator();
		}
		#endregion

	}
}
